use std::collections::VecDeque;

use Reg::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Reg {
    W = 0, X, Y, Z
}

#[derive(Debug)]
enum Instruction {
    Inp(Reg),
    Add(Reg, Reg),
    Mul(Reg, Reg),
    Div(Reg, Reg),
    Mod(Reg, Reg),
    Eql(Reg, Reg),
    AddI(Reg, i32),
    MulI(Reg, i32),
    DivI(Reg, i32),
    ModI(Reg, i32),
    EqlI(Reg, i32),
}

impl From<&str> for Instruction {
    fn from(value: &str) -> Self {
        fn char_to_reg(ch: char) -> Reg {
            match ch {
                'w' => W,
                'x' => X,
                'y' => Y,
                'z' => Z,
                _ => panic!("no reg: {}", ch),
            }
        }

        enum Arg {
            R(Reg),
            I(i32),
        }

        fn parse_arg(s: &str) -> Arg {
            let ch = s.chars().nth(0).unwrap();
            if ch.is_digit(10) || ch == '-' {
                Arg::I(s.parse().unwrap())
            } else {
                Arg::R(char_to_reg(ch))
            }
        }

        let rd: Reg = char_to_reg(value.chars().nth(4).unwrap());
        if &value[..3] == "inp" {
            Instruction::Inp(rd)
        } else {
            let arg = parse_arg(&value[6..]);
            match &value[..3] {
                "add" => {
                    match arg {
                        Arg::R(rs) => Instruction::Add(rd, rs),
                        Arg::I(imm) => Instruction::AddI(rd, imm),
                    }
                }
                "mul" => {
                    match arg {
                        Arg::R(rs) => Instruction::Mul(rd, rs),
                        Arg::I(imm) => Instruction::MulI(rd, imm),
                    }
                }
                "div" => {
                    match arg {
                        Arg::R(rs) => Instruction::Div(rd, rs),
                        Arg::I(imm) => Instruction::DivI(rd, imm),
                    }
                }
                "mod" => {
                    match arg {
                        Arg::R(rs) => Instruction::Mod(rd, rs),
                        Arg::I(imm) => Instruction::ModI(rd, imm),
                    }
                }
                "eql" => {
                    match arg {
                        Arg::R(rs) => Instruction::Eql(rd, rs),
                        Arg::I(imm) => Instruction::EqlI(rd, imm),
                    }
                }
                _ => panic!("no inst")
            }
        }
    }
}

fn parse_input() -> Vec<Instruction> {
    let input = std::fs::read_to_string("./day23/input.txt").unwrap();
    input.lines().map(|line| line.into()).collect()
}
struct ALU {
    regs: [i32; 4]
}

impl ALU {
    fn new() -> Self {
        Self {
            regs: [0, 0, 0, 0]
        }
    }

    fn reset(&mut self) {
        self.regs[0] = 0;
        self.regs[1] = 0;
        self.regs[2] = 0;
        self.regs[3] = 0;
    }

    fn get_reg(&self, reg: Reg) -> i32 {
        self.regs[reg as usize]
    }

    fn set_reg(&mut self, reg: Reg, value: i32) {
        self.regs[reg as usize] = value;
    }

    fn execute(&mut self, input: &mut VecDeque<i32>, inst: &Instruction) {
        match inst {
            Instruction::Inp(reg) => self.set_reg(*reg, input.pop_front().unwrap()),
            Instruction::Add(rd, rs) => self.set_reg(*rd, self.get_reg(*rd) + self.get_reg(*rs)),
            Instruction::Mul(rd, rs) => self.set_reg(*rd, self.get_reg(*rd) * self.get_reg(*rs)),
            Instruction::Div(rd, rs) => self.set_reg(*rd, self.get_reg(*rd) / self.get_reg(*rs)),
            Instruction::Mod(rd, rs) => self.set_reg(*rd, self.get_reg(*rd) % self.get_reg(*rs)),
            Instruction::Eql(rd, rs) => self.set_reg(*rd, if self.get_reg(*rd) == self.get_reg(*rs) {1} else {0}),
            Instruction::AddI(rd, imm) => self.set_reg(*rd, self.get_reg(*rd) + imm),
            Instruction::MulI(rd, imm) => self.set_reg(*rd, self.get_reg(*rd) * imm),
            Instruction::DivI(rd, imm) => self.set_reg(*rd, self.get_reg(*rd) / imm),
            Instruction::ModI(rd, imm) => self.set_reg(*rd, self.get_reg(*rd) % imm),
            Instruction::EqlI(rd, imm) => self.set_reg(*rd, if self.get_reg(*rd) == *imm {1} else {0}),
        }
    }

    fn run(&mut self, mut input: VecDeque<i32>, insts: &Vec<Instruction>) {
        self.reset();
        for inst in insts {
            self.execute(&mut input, inst);
        }
    }

    fn check(&self) -> bool {
        self.get_reg(Z) == 0
    }

}

#[allow(unused)]
fn part1(insts: &Vec<Instruction>) {
    let mut alu = ALU::new();
}

fn main() {
    let insts = parse_input();
    part1(&insts);
}
